/*
 * This file is part of din.
 *
 * din is copyright (c) 2006 - 2012 S Jagannathan <jag@dinisnoise.org>
 * For more information, please visit http://dinisnoise.org
 *
 * din is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * din is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with din.  If not, see <http://www.gnu.org/licenses/>.
 *
*/
#include "curve.h"
#include "vector2d.h"
#include "utils.h"
#include "color.h"

#include <list>
using namespace std;

curve::curve () {
  for (int i = 0; i < 2; ++i) x[i] = y[i] = tx[i] = ty[i] = 0;
  set_limit (1);
}

void curve::copy (const curve& s) {

  if (this != &s) {
		
    for (int i = 0; i < 2; ++i) {
      x[i] = s.x[i];
      y[i] = s.y[i];
      tx[i] = s.tx[i];
      ty[i] = s.ty[i];
    }

    lpts.clear (); for (list<crvpt>::const_iterator p = s.lpts.begin (), q = s.lpts.end (); p != q; ++p) lpts.push_back (*p);
    vpts.clear (); for (int i = 0, j = s.vpts.size(); i < j; ++i) vpts.push_back (s.vpts[i]);

    limit2 = s.limit2;
    eval_state = s.eval_state;

  }

}

curve::curve (const curve& s) {
  copy (s);
}

curve& curve::operator= (const curve& s) {
  copy (s);
  return *this;
}

curve::curve (float x1, float y1, float x2, float y2, float tx1, float ty1, float tx2, float ty2, float d) {
  x[0] = x1; x[1] = x2;
  y[0] = y1; y[1] = y2;
  tx[0] = tx1; tx[1] = tx2;
  ty[0] = ty1; ty[1] = ty2;
  set_limit (d);
}

void curve::set_limit (float l) {
  limit2 = l * l;
  eval_state = EVAL_REQUIRED;
}

void curve::eval () {
	
	/*
	 * generates points of the curve using recursive subdivision
	 * 
	*/
	
  lpts.clear (); // clear existing points
	
	// add end points to curve
  lpts.push_back (crvpt (x[0], y[0], 0)); // x0, y0, t0
  lpts.push_back (crvpt (x[1], y[1], 1)); // x1, y1, t1
	
  list<crvpt>::iterator i (lpts.begin ());
  list<crvpt>::iterator j (++lpts.begin());
	
	// generate other points of the curve
  crvpt r;
  while (j != lpts.end()) {

		// end points
    crvpt& p = *i;
    crvpt& q = *j;

		// mid point of line joining end points
    float mx = (p.x + q.x) / 2, my = (p.y + q.y) / 2;

		// T of parametric bezier curve equation
    float T = (p.t + q.t) / 2; 
    
    // calculate various co-efficients of the bezier curve
    float T1 = 1 - T;
    float T1_2 = T1 * T1;
    float T1_3 = T1_2 * T1;
    float T_2 = T * T;
    float T_3 = T_2 * T;
    float BP1 = T1_3;
    float BT1 = 3 * T * T1_2;
    float BT2 = 3 * T_2 * T1;
    float BP2 = T_3;
		
		// calculate point on the bezier curve
    float bx = BP1 * x[0] + BT1 * tx[0] + BT2 * tx[1] + BP2 * x[1];
    float by = BP1 * y[0] + BT1 * ty[0] + BT2 * ty[1] + BP2 * y[1];
		
		// distance between mid point of line joining end points & point on the bezier curve
    float d2 = magnitude2 (mx, my, bx, by);

    if (d2 >= limit2) { // insert point on bezier curve & further subdivide
      
      r.x = bx; 
      r.y = by; 
      r.t = T; 
      
      r.m = 0; 
      r.inf = 0;
      
      j = lpts.insert (j, r); // new right hand end point
    
    } else { // limit reached. no more inserts
          
      // calc slope of left end point
      float dx = q.x - p.x;
      if (dx == 0) { 
        p.m = 0;
        p.inf = 1; // slope is infinite
      } else {
        float dy = q.y - p.y;
        if (dy == 0) {
          p.m = 0;
          p.inf = 0;
        } else {
          p.m = dy / dx;
          p.inf = -1;
        }
      }
      i = j; // right end point is now left end point
      ++j; // new right end point
      
      // we find slope of all points except last point (that is OK)
    
    }

  }

  vpts.clear (); for (list<crvpt>::const_iterator p = lpts.begin (), q = lpts.end (); p != q; ++p) vpts.push_back (*p);

  eval_state = EVAL_COMPLETE;

}
